#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import pwd
import shutil
import stat

from common.common import execute_cmd
from common.const import SysData, CMDResult
from common.logger import Logger
from common.number_const import NumberConst
from generaldb.saphana.comm.saphana_const import SaphanaPath, SaphanaErrorCode, SaphanaDbActionType
from saphana.resource.saphana_cluster_manager import SaphanaClusterManage
from saphana.common.param_parse import Param

LOGGER = Logger().get_logger(filename="saphana.log")


class HanaCommonUtil:
    @staticmethod
    def get_instance_id(sid):
        cmd = f"su - {sid}adm -c 'echo $TINSTANCE'"
        ret, output, err = execute_cmd(cmd)
        if ret == CMDResult.SUCCESS:
            LOGGER.debug(f"The instance id is %s", output)
            return True, output.strip()
        return False, ""

    @staticmethod
    def safe_get_environ(key):
        if not SysData.SYS_STDIN:
            return ""
        try:
            value_dict = json.loads(SysData.SYS_STDIN)
        except Exception:
            return ""
        value = value_dict.get(key)
        return value if isinstance(value, str) else ""

    @staticmethod
    def login_database(*args):
        if len(args) != NumberConst.SIX:
            return False, False, ""
        db_name, username, db_pwd_env, sid, system_db_port, query_cmd_list = args
        ret, instance_id = HanaCommonUtil.get_instance_id(sid)
        if not ret:
            return False, False, ""
        # 获取所有节点列表
        cmd = f"su - {sid}adm -c 'sapcontrol -nr {instance_id} -function GetSystemInstanceList'"
        ret, output, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            LOGGER.error(f"Fail to get instance list for %s", err)
            return False, False, ""
        host_list = [item.split(',')[0] for item in output.split('\n') if "HDB" in item]
        if not host_list:
            LOGGER.error(f"Fail to get instance list with output : %s", output)
            return False, False, ""
        return_res = False
        output = ""
        ac_type = SaphanaDbActionType.PROTECT_CMD
        for host in host_list:
            cmd = f"su - {sid}adm -c " \
                  f"'hdbsql -i {instance_id} -n {host}:{system_db_port} -d {db_name} -u {username}'"
            login_ret, cmd_exec_ret, output = SaphanaClusterManage.exec_interactive_cmd(db_name, cmd, db_pwd_env,
                                                                                        query_cmd_list,
                                                                                        ac_type)
            if cmd_exec_ret == SaphanaErrorCode.DB_CONFIG_FAILED:
                return return_res, cmd_exec_ret, output
            if login_ret:
                LOGGER.info("Login successfully.")
                return_res = login_ret and cmd_exec_ret
                break
        return return_res, return_res, output

    @staticmethod
    def copy_backint(instance_dir):
        path = "/opt/DataBackup/ProtectClient/Plugins/GeneralDBPlugin/bin/backint"
        backint_path = f"{instance_dir}/SYS/global/hdb/opt/opbackint"
        if os.path.exists(backint_path):
            shutil.rmtree(backint_path)
            os.mkdir(backint_path)
        for root, dirs, files in os.walk(path):
            for file in files:
                src_file = os.path.join(root, file)
                dst_file = os.path.join(
                    backint_path, os.path.relpath(src_file, path))
                dst_dir_path = os.path.dirname(dst_file)
                if not os.path.exists(dst_dir_path):
                    os.makedirs(dst_dir_path)
                shutil.copy2(src_file, dst_file)
            for dr in dirs:
                src_dir_path = os.path.join(root, dr)
                dst_dir_path = os.path.join(
                    backint_path, os.path.relpath(src_dir_path, path))
                shutil.copytree(src_dir_path, dst_dir_path)
        LOGGER.info("Copy backint script end.")
        return True

    @staticmethod
    def installing_the_backint(sid, instance_dir, need_set_conf=True):
        sid = sid.upper()
        path = "/opt/DataBackup/ProtectClient/Plugins/GeneralDBPlugin/bin/backint"
        lib_path = f"/opt/DataBackup/ProtectClient/Plugins/GeneralDBPlugin/lib"
        if not os.path.exists(path) or not os.path.exists(lib_path):
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.PATH_NOT_EXIST, "Backint path is not exist!")
            return False, params
        base_path = f"{instance_dir}/SYS/global/hdb/opt"
        if not os.path.exists(base_path):
            os.mkdir(base_path)
        # 拷贝backint程序
        if not HanaCommonUtil.copy_backint(instance_dir):
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.INSTALL_BACKINT_FAILED, "Copy backint script error")
            return False, params
        # 拷贝公共库
        backint_lib_path = f"{instance_dir}/SYS/global/hdb/opt/opbackint/lib"
        com_libs = ['libcommon_util.so', 'libconfig_reader_util.so', 'liblog_util.so']
        for file in com_libs:
            src_file = os.path.join(lib_path, file)
            dst_file = os.path.join(backint_lib_path, file)
            shutil.copy2(src_file, dst_file)
        modify_script_cmd = f"sed -i \"s/SID/{sid}/g\" {instance_dir}/SYS/global/hdb/opt/opbackint/backint.sh"
        ret, _, _ = execute_cmd(modify_script_cmd)
        if ret != CMDResult.SUCCESS:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.INSTALL_BACKINT_FAILED, "Modify backint script error")
            return False, params
        # 创建软链接
        backint_path = f"{instance_dir}/SYS/global/hdb/opt/opbackint"
        hdbbackint_path = f"{base_path}/hdbbackint"
        if os.path.exists(hdbbackint_path):
            os.remove(hdbbackint_path)
        ln_cmd = f"ln -s {backint_path}/backint.sh {hdbbackint_path}"
        ret, _, _ = execute_cmd(ln_cmd)
        if not ret:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.INSTALL_BACKINT_FAILED, "Failed to create soft link")
            LOGGER.info("Failed to create soft link.")
            return False, params
        # 安装配置文件
        hdbconfig_path = os.path.join(base_path, "hdbconfig")
        if not os.path.exists(hdbconfig_path):
            os.mkdir(hdbconfig_path)
        if need_set_conf:
            shutil.copy(f"{path}/backint.conf", hdbconfig_path)
        # 修改opt目录权限
        user_info = pwd.getpwnam(f"{sid.lower()}adm")
        chown_cmd = f"chown -R {user_info.pw_uid}:{user_info.pw_gid} {base_path}"
        ret, _, _ = execute_cmd(chown_cmd)
        if ret != CMDResult.SUCCESS:
            params = Param.get_body_error_param(SaphanaErrorCode.ERROR_FOR_FRAMEWORK,
                                                SaphanaErrorCode.INSTALL_BACKINT_FAILED, "Failed to modify permission")
            return False, params
        return True, ""

    @staticmethod
    def check_logpath(sid, log_path):
        if not os.path.exists(log_path):
            return False
        user_info = pwd.getpwnam(f"{sid}adm")
        uid = user_info.pw_uid
        gid = user_info.pw_gid
        s = os.stat(log_path)
        mode = s[stat.ST_MODE]
        return all([
            (s[stat.ST_UID] == uid and (mode & stat.S_IRUSR > 0))
            or (s[stat.ST_GID] == gid and (mode & stat.S_IRGRP > 0))
            or (mode & stat.S_IROTH > 0),
            (s[stat.ST_UID] == uid and (mode & stat.S_IWUSR > 0))
            or (s[stat.ST_GID] == gid and (mode & stat.S_IWGRP > 0))
            or (mode & stat.S_IWOTH > 0)
        ])

    @staticmethod
    def check_backint_tool(sid, instance_dir):
        for file in ["backint"]:
            tool_path = os.path.join(SaphanaPath.BACKINT_PATH, file)
            ret, std_out, _ = execute_cmd(f"md5sum {tool_path}")
            if ret != CMDResult.SUCCESS:
                LOGGER.error("Failed to get backint md5 sum")
                return False
            new_tool_sum = std_out.split()[0]
            sap_tool_path = os.path.join(instance_dir, "SYS/global/hdb/opt/opbackint", file)
            ret, std_out, _ = execute_cmd(f"md5sum {sap_tool_path}")
            if ret != CMDResult.SUCCESS:
                LOGGER.warn(f"Fail to get sap backint md5: %s", std_out)
                ret, _ = HanaCommonUtil.installing_the_backint(
                    sid, instance_dir, False)
                break
            old_tool_sum = std_out.split()[0]
            if new_tool_sum == old_tool_sum:
                continue
            LOGGER.error(f"The file: %s is not same, need to reinstall backint", file)
            ret, _ = HanaCommonUtil.installing_the_backint(sid, instance_dir, False)
            break
        LOGGER.info(f"The result of prepare backint is ret: %s", ret)
        return ret
